import { AnimationClip, BufferGeometry, FileLoader, Float32BufferAttribute, Loader, Vector3 } from 'three'

const _normalData = [
  [-0.525731, 0.0, 0.850651],
  [-0.442863, 0.238856, 0.864188],
  [-0.295242, 0.0, 0.955423],
  [-0.309017, 0.5, 0.809017],
  [-0.16246, 0.262866, 0.951056],
  [0.0, 0.0, 1.0],
  [0.0, 0.850651, 0.525731],
  [-0.147621, 0.716567, 0.681718],
  [0.147621, 0.716567, 0.681718],
  [0.0, 0.525731, 0.850651],
  [0.309017, 0.5, 0.809017],
  [0.525731, 0.0, 0.850651],
  [0.295242, 0.0, 0.955423],
  [0.442863, 0.238856, 0.864188],
  [0.16246, 0.262866, 0.951056],
  [-0.681718, 0.147621, 0.716567],
  [-0.809017, 0.309017, 0.5],
  [-0.587785, 0.425325, 0.688191],
  [-0.850651, 0.525731, 0.0],
  [-0.864188, 0.442863, 0.238856],
  [-0.716567, 0.681718, 0.147621],
  [-0.688191, 0.587785, 0.425325],
  [-0.5, 0.809017, 0.309017],
  [-0.238856, 0.864188, 0.442863],
  [-0.425325, 0.688191, 0.587785],
  [-0.716567, 0.681718, -0.147621],
  [-0.5, 0.809017, -0.309017],
  [-0.525731, 0.850651, 0.0],
  [0.0, 0.850651, -0.525731],
  [-0.238856, 0.864188, -0.442863],
  [0.0, 0.955423, -0.295242],
  [-0.262866, 0.951056, -0.16246],
  [0.0, 1.0, 0.0],
  [0.0, 0.955423, 0.295242],
  [-0.262866, 0.951056, 0.16246],
  [0.238856, 0.864188, 0.442863],
  [0.262866, 0.951056, 0.16246],
  [0.5, 0.809017, 0.309017],
  [0.238856, 0.864188, -0.442863],
  [0.262866, 0.951056, -0.16246],
  [0.5, 0.809017, -0.309017],
  [0.850651, 0.525731, 0.0],
  [0.716567, 0.681718, 0.147621],
  [0.716567, 0.681718, -0.147621],
  [0.525731, 0.850651, 0.0],
  [0.425325, 0.688191, 0.587785],
  [0.864188, 0.442863, 0.238856],
  [0.688191, 0.587785, 0.425325],
  [0.809017, 0.309017, 0.5],
  [0.681718, 0.147621, 0.716567],
  [0.587785, 0.425325, 0.688191],
  [0.955423, 0.295242, 0.0],
  [1.0, 0.0, 0.0],
  [0.951056, 0.16246, 0.262866],
  [0.850651, -0.525731, 0.0],
  [0.955423, -0.295242, 0.0],
  [0.864188, -0.442863, 0.238856],
  [0.951056, -0.16246, 0.262866],
  [0.809017, -0.309017, 0.5],
  [0.681718, -0.147621, 0.716567],
  [0.850651, 0.0, 0.525731],
  [0.864188, 0.442863, -0.238856],
  [0.809017, 0.309017, -0.5],
  [0.951056, 0.16246, -0.262866],
  [0.525731, 0.0, -0.850651],
  [0.681718, 0.147621, -0.716567],
  [0.681718, -0.147621, -0.716567],
  [0.850651, 0.0, -0.525731],
  [0.809017, -0.309017, -0.5],
  [0.864188, -0.442863, -0.238856],
  [0.951056, -0.16246, -0.262866],
  [0.147621, 0.716567, -0.681718],
  [0.309017, 0.5, -0.809017],
  [0.425325, 0.688191, -0.587785],
  [0.442863, 0.238856, -0.864188],
  [0.587785, 0.425325, -0.688191],
  [0.688191, 0.587785, -0.425325],
  [-0.147621, 0.716567, -0.681718],
  [-0.309017, 0.5, -0.809017],
  [0.0, 0.525731, -0.850651],
  [-0.525731, 0.0, -0.850651],
  [-0.442863, 0.238856, -0.864188],
  [-0.295242, 0.0, -0.955423],
  [-0.16246, 0.262866, -0.951056],
  [0.0, 0.0, -1.0],
  [0.295242, 0.0, -0.955423],
  [0.16246, 0.262866, -0.951056],
  [-0.442863, -0.238856, -0.864188],
  [-0.309017, -0.5, -0.809017],
  [-0.16246, -0.262866, -0.951056],
  [0.0, -0.850651, -0.525731],
  [-0.147621, -0.716567, -0.681718],
  [0.147621, -0.716567, -0.681718],
  [0.0, -0.525731, -0.850651],
  [0.309017, -0.5, -0.809017],
  [0.442863, -0.238856, -0.864188],
  [0.16246, -0.262866, -0.951056],
  [0.238856, -0.864188, -0.442863],
  [0.5, -0.809017, -0.309017],
  [0.425325, -0.688191, -0.587785],
  [0.716567, -0.681718, -0.147621],
  [0.688191, -0.587785, -0.425325],
  [0.587785, -0.425325, -0.688191],
  [0.0, -0.955423, -0.295242],
  [0.0, -1.0, 0.0],
  [0.262866, -0.951056, -0.16246],
  [0.0, -0.850651, 0.525731],
  [0.0, -0.955423, 0.295242],
  [0.238856, -0.864188, 0.442863],
  [0.262866, -0.951056, 0.16246],
  [0.5, -0.809017, 0.309017],
  [0.716567, -0.681718, 0.147621],
  [0.525731, -0.850651, 0.0],
  [-0.238856, -0.864188, -0.442863],
  [-0.5, -0.809017, -0.309017],
  [-0.262866, -0.951056, -0.16246],
  [-0.850651, -0.525731, 0.0],
  [-0.716567, -0.681718, -0.147621],
  [-0.716567, -0.681718, 0.147621],
  [-0.525731, -0.850651, 0.0],
  [-0.5, -0.809017, 0.309017],
  [-0.238856, -0.864188, 0.442863],
  [-0.262866, -0.951056, 0.16246],
  [-0.864188, -0.442863, 0.238856],
  [-0.809017, -0.309017, 0.5],
  [-0.688191, -0.587785, 0.425325],
  [-0.681718, -0.147621, 0.716567],
  [-0.442863, -0.238856, 0.864188],
  [-0.587785, -0.425325, 0.688191],
  [-0.309017, -0.5, 0.809017],
  [-0.147621, -0.716567, 0.681718],
  [-0.425325, -0.688191, 0.587785],
  [-0.16246, -0.262866, 0.951056],
  [0.442863, -0.238856, 0.864188],
  [0.16246, -0.262866, 0.951056],
  [0.309017, -0.5, 0.809017],
  [0.147621, -0.716567, 0.681718],
  [0.0, -0.525731, 0.850651],
  [0.425325, -0.688191, 0.587785],
  [0.587785, -0.425325, 0.688191],
  [0.688191, -0.587785, 0.425325],
  [-0.955423, 0.295242, 0.0],
  [-0.951056, 0.16246, 0.262866],
  [-1.0, 0.0, 0.0],
  [-0.850651, 0.0, 0.525731],
  [-0.955423, -0.295242, 0.0],
  [-0.951056, -0.16246, 0.262866],
  [-0.864188, 0.442863, -0.238856],
  [-0.951056, 0.16246, -0.262866],
  [-0.809017, 0.309017, -0.5],
  [-0.864188, -0.442863, -0.238856],
  [-0.951056, -0.16246, -0.262866],
  [-0.809017, -0.309017, -0.5],
  [-0.681718, 0.147621, -0.716567],
  [-0.681718, -0.147621, -0.716567],
  [-0.850651, 0.0, -0.525731],
  [-0.688191, 0.587785, -0.425325],
  [-0.587785, 0.425325, -0.688191],
  [-0.425325, 0.688191, -0.587785],
  [-0.425325, -0.688191, -0.587785],
  [-0.587785, -0.425325, -0.688191],
  [-0.688191, -0.587785, -0.425325],
]

class MD2Loader extends Loader {
  constructor(manager) {
    super(manager)
  }

  load(url, onLoad, onProgress, onError) {
    const scope = this

    const loader = new FileLoader(scope.manager)
    loader.setPath(scope.path)
    loader.setResponseType('arraybuffer')
    loader.setRequestHeader(scope.requestHeader)
    loader.setWithCredentials(scope.withCredentials)
    loader.load(
      url,
      function (buffer) {
        try {
          onLoad(scope.parse(buffer))
        } catch (e) {
          if (onError) {
            onError(e)
          } else {
            console.error(e)
          }

          scope.manager.itemError(url)
        }
      },
      onProgress,
      onError,
    )
  }

  parse(buffer) {
    const data = new DataView(buffer)

    // http://tfc.duke.free.fr/coding/md2-specs-en.html

    const header = {}
    const headerNames = [
      'ident',
      'version',
      'skinwidth',
      'skinheight',
      'framesize',
      'num_skins',
      'num_vertices',
      'num_st',
      'num_tris',
      'num_glcmds',
      'num_frames',
      'offset_skins',
      'offset_st',
      'offset_tris',
      'offset_frames',
      'offset_glcmds',
      'offset_end',
    ]

    for (let i = 0; i < headerNames.length; i++) {
      header[headerNames[i]] = data.getInt32(i * 4, true)
    }

    if (header.ident !== 844121161 || header.version !== 8) {
      console.error('Not a valid MD2 file')
      return
    }

    if (header.offset_end !== data.byteLength) {
      console.error('Corrupted MD2 file')
      return
    }

    //

    const geometry = new BufferGeometry()

    // uvs

    const uvsTemp = []
    let offset = header.offset_st

    for (let i = 0, l = header.num_st; i < l; i++) {
      const u = data.getInt16(offset + 0, true)
      const v = data.getInt16(offset + 2, true)

      uvsTemp.push(u / header.skinwidth, 1 - v / header.skinheight)

      offset += 4
    }

    // triangles

    offset = header.offset_tris

    const vertexIndices = []
    const uvIndices = []

    for (let i = 0, l = header.num_tris; i < l; i++) {
      vertexIndices.push(
        data.getUint16(offset + 0, true),
        data.getUint16(offset + 2, true),
        data.getUint16(offset + 4, true),
      )

      uvIndices.push(
        data.getUint16(offset + 6, true),
        data.getUint16(offset + 8, true),
        data.getUint16(offset + 10, true),
      )

      offset += 12
    }

    // frames

    const translation = new Vector3()
    const scale = new Vector3()
    const string = []

    const frames = []

    offset = header.offset_frames

    for (let i = 0, l = header.num_frames; i < l; i++) {
      scale.set(data.getFloat32(offset + 0, true), data.getFloat32(offset + 4, true), data.getFloat32(offset + 8, true))

      translation.set(
        data.getFloat32(offset + 12, true),
        data.getFloat32(offset + 16, true),
        data.getFloat32(offset + 20, true),
      )

      offset += 24

      for (let j = 0; j < 16; j++) {
        const character = data.getUint8(offset + j, true)
        if (character === 0) break

        string[j] = character
      }

      const frame = {
        name: String.fromCharCode.apply(null, string),
        vertices: [],
        normals: [],
      }

      offset += 16

      for (let j = 0; j < header.num_vertices; j++) {
        let x = data.getUint8(offset++, true)
        let y = data.getUint8(offset++, true)
        let z = data.getUint8(offset++, true)
        const n = _normalData[data.getUint8(offset++, true)]

        x = x * scale.x + translation.x
        y = y * scale.y + translation.y
        z = z * scale.z + translation.z

        frame.vertices.push(x, z, y) // convert to Y-up
        frame.normals.push(n[0], n[2], n[1]) // convert to Y-up
      }

      frames.push(frame)
    }

    // static

    const positions = []
    const normals = []
    const uvs = []

    const verticesTemp = frames[0].vertices
    const normalsTemp = frames[0].normals

    for (let i = 0, l = vertexIndices.length; i < l; i++) {
      const vertexIndex = vertexIndices[i]
      let stride = vertexIndex * 3

      //

      const x = verticesTemp[stride]
      const y = verticesTemp[stride + 1]
      const z = verticesTemp[stride + 2]

      positions.push(x, y, z)

      //

      const nx = normalsTemp[stride]
      const ny = normalsTemp[stride + 1]
      const nz = normalsTemp[stride + 2]

      normals.push(nx, ny, nz)

      //

      const uvIndex = uvIndices[i]
      stride = uvIndex * 2

      const u = uvsTemp[stride]
      const v = uvsTemp[stride + 1]

      uvs.push(u, v)
    }

    geometry.setAttribute('position', new Float32BufferAttribute(positions, 3))
    geometry.setAttribute('normal', new Float32BufferAttribute(normals, 3))
    geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2))

    // animation

    const morphPositions = []
    const morphNormals = []

    for (let i = 0, l = frames.length; i < l; i++) {
      const frame = frames[i]
      const attributeName = frame.name

      if (frame.vertices.length > 0) {
        const positions = []

        for (let j = 0, jl = vertexIndices.length; j < jl; j++) {
          const vertexIndex = vertexIndices[j]
          const stride = vertexIndex * 3

          const x = frame.vertices[stride]
          const y = frame.vertices[stride + 1]
          const z = frame.vertices[stride + 2]

          positions.push(x, y, z)
        }

        const positionAttribute = new Float32BufferAttribute(positions, 3)
        positionAttribute.name = attributeName

        morphPositions.push(positionAttribute)
      }

      if (frame.normals.length > 0) {
        const normals = []

        for (let j = 0, jl = vertexIndices.length; j < jl; j++) {
          const vertexIndex = vertexIndices[j]
          const stride = vertexIndex * 3

          const nx = frame.normals[stride]
          const ny = frame.normals[stride + 1]
          const nz = frame.normals[stride + 2]

          normals.push(nx, ny, nz)
        }

        const normalAttribute = new Float32BufferAttribute(normals, 3)
        normalAttribute.name = attributeName

        morphNormals.push(normalAttribute)
      }
    }

    geometry.morphAttributes.position = morphPositions
    geometry.morphAttributes.normal = morphNormals
    geometry.morphTargetsRelative = false

    geometry.animations = AnimationClip.CreateClipsFromMorphTargetSequences(frames, 10)

    return geometry
  }
}

export { MD2Loader }
